// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#ifndef ZIRCON_KERNEL_DEV_PCIE_INCLUDE_DEV_PCI_CONFIG_H_
#define ZIRCON_KERNEL_DEV_PCIE_INCLUDE_DEV_PCI_CONFIG_H_

#include <debug.h>
#include <endian.h>

#include <dev/pci_common.h>
#include <fbl/intrusive_single_list.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>

class PciReg8 {
 public:
  constexpr explicit PciReg8(uint16_t offset) : offset_(offset) {}
  constexpr PciReg8() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }

 private:
  uint16_t offset_;
};

class PciReg16 {
 public:
  constexpr explicit PciReg16(uint16_t offset) : offset_(offset) {}
  constexpr PciReg16() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }

 private:
  uint16_t offset_;
};

class PciReg32 {
 public:
  constexpr explicit PciReg32(uint16_t offset) : offset_(offset) {}
  constexpr PciReg32() : offset_(0u) {}
  constexpr uint16_t offset() const { return offset_; }

 private:
  uint16_t offset_;
};

/* PciConfig supplies the factory for creating the appropriate pci config
 * object based on the address space of the pci device. */
class PciConfig : public fbl::SinglyLinkedListable<fbl::RefPtr<PciConfig>>,
                  public fbl::RefCounted<PciConfig> {
 public:
  // Standard PCI configuration space values. Offsets from PCI Firmware Spec ch 6.
  static constexpr PciReg16 kVendorId = PciReg16(0x0);
  static constexpr PciReg16 kDeviceId = PciReg16(0x2);
  static constexpr PciReg16 kCommand = PciReg16(0x4);
  static constexpr PciReg16 kStatus = PciReg16(0x6);
  static constexpr PciReg8 kRevisionId = PciReg8(0x8);
  static constexpr PciReg8 kProgramInterface = PciReg8(0x9);
  static constexpr PciReg8 kSubClass = PciReg8(0xA);
  static constexpr PciReg8 kBaseClass = PciReg8(0xB);
  static constexpr PciReg8 kCacheLineSize = PciReg8(0xC);
  static constexpr PciReg8 kLatencyTimer = PciReg8(0xD);
  static constexpr PciReg8 kHeaderType = PciReg8(0xE);
  static constexpr PciReg8 kBist = PciReg8(0xF);
  /* 0x10 is the address of the first BAR in config space
   * BAR rather than BaseAddress for space / sanity considerations */
  static constexpr PciReg32 kBAR(uint bar) {
    DEBUG_ASSERT(bar < PCIE_MAX_BAR_REGS);
    return PciReg32(static_cast<uint16_t>(0x10 + (bar * sizeof(uint32_t))));
  }
  static constexpr PciReg32 kCardbusCisPtr = PciReg32(0x28);
  static constexpr PciReg16 kSubsystemVendorId = PciReg16(0x2C);
  static constexpr PciReg16 kSubsystemId = PciReg16(0x2E);
  static constexpr PciReg32 kExpansionRomAddress = PciReg32(0x30);
  static constexpr PciReg8 kCapabilitiesPtr = PciReg8(0x34);
  // 0x35 through 0x3B is reserved
  static constexpr PciReg8 kInterruptLine = PciReg8(0x3C);
  static constexpr PciReg8 kInterruptPin = PciReg8(0x3D);
  static constexpr PciReg8 kMinGrant = PciReg8(0x3E);
  static constexpr PciReg8 kMaxLatency = PciReg8(0x3F);
  static constexpr uint8_t kStdCfgEnd =
      static_cast<uint8_t>(kMaxLatency.offset() + sizeof(uint8_t));

  /* pci to pci bridge config
   * Unlike a normal PCI header, a bridge only has two BARs, but the BAR offset in config space
   * is the same. */
  static constexpr PciReg8 kPrimaryBusId = PciReg8(0x18);
  static constexpr PciReg8 kSecondaryBusId = PciReg8(0x19);
  static constexpr PciReg8 kSubordinateBusId = PciReg8(0x1A);
  static constexpr PciReg8 kSecondaryLatencyTimer = PciReg8(0x1B);
  static constexpr PciReg8 kIoBase = PciReg8(0x1C);
  static constexpr PciReg8 kIoLimit = PciReg8(0x1D);
  static constexpr PciReg16 kSecondaryStatus = PciReg16(0x1E);
  static constexpr PciReg16 kMemoryBase = PciReg16(0x20);
  static constexpr PciReg16 kMemoryLimit = PciReg16(0x22);
  static constexpr PciReg16 kPrefetchableMemoryBase = PciReg16(0x24);
  static constexpr PciReg16 kPrefetchableMemoryLimit = PciReg16(0x26);
  static constexpr PciReg32 kPrefetchableMemoryBaseUpper = PciReg32(0x28);
  static constexpr PciReg32 kPrefetchableMemoryLimitUpper = PciReg32(0x2C);
  static constexpr PciReg16 kIoBaseUpper = PciReg16(0x30);
  static constexpr PciReg16 kIoLimitUpper = PciReg16(0x32);
  // Capabilities Pointer for a bridge matches the standard 0x34 offset
  // 0x35 through 0x38 is reserved
  static constexpr PciReg32 kBridgeExpansionRomAddress = PciReg32(0x38);
  // interrupt line for a bridge matches the standard 0x3C offset
  // interrupt pin for a bridge matches the standard 0x3D offset
  static constexpr PciReg16 kBridgeControl = PciReg16(0x3E);

  /** Create a Pci Configuration object of the appropriate type.
   *
   * @param base The base address for the PCI configuration space.  @param
   * @param addr_type An enum value of PciAddrSpace to identify the time of address
   * space the configuration object will use.
   *
   * @return a pointer to a new PciConfig instance on success, nullptr on failure.
   */
  static fbl::RefPtr<PciConfig> Create(uintptr_t base, PciAddrSpace addr_type);
  inline uintptr_t base() const { return base_; }
  inline PciAddrSpace addr_space() const { return addr_space_; }

  // Virtuals
  void DumpConfig(uint16_t len) const;
  virtual uint8_t Read(const PciReg8 addr) const = 0;
  virtual uint16_t Read(const PciReg16 addr) const = 0;
  virtual uint32_t Read(const PciReg32 addr) const = 0;
  virtual void Write(const PciReg8 addr, uint8_t val) const = 0;
  virtual void Write(const PciReg16 addr, uint16_t val) const = 0;
  virtual void Write(const PciReg32 addr, uint32_t val) const = 0;
  virtual ~PciConfig() {}

 protected:
  PciConfig(uintptr_t base, PciAddrSpace addr_space) : addr_space_(addr_space), base_(base) {}
  const PciAddrSpace addr_space_;
  const uintptr_t base_;
};

#endif  // ZIRCON_KERNEL_DEV_PCIE_INCLUDE_DEV_PCI_CONFIG_H_
